﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.ServiceModel;

using OCXS;
using OCXS.OCXSServiceLibrary.OPCXMLDA10;
using Conf = OCXS.OCXSServiceLibrary.Configuration;

namespace OCXS {

	namespace OCXSServiceLibrary {

		public partial class OCXSService {

			/// <summary>
            /// <para>
            /// Implements OPC XML DA 1.0 Browse service. 
            /// </para>
            /// <para>
            /// See OPC XML DA 1.0 specification for more information about Browse service.
            /// </para>
			/// </summary>
            /// <remarks>
            /// <para>
            ///     <b>Following there's a list of implemented (or not) standard features.</b>
            /// </para>
            /// <para>
            ///     The following <c>BrowseRequest</c> standard features are (or not) implemented.
            ///     <ul>
            ///         <li><b>BrowseFilter: </b> not supported (assumed <c>all</c>).</li>
            ///         <li><b>ClientRequestHandle: </b> supported.</li>
            ///         <li><b>ContinuationPoint: </b> not supported.</li>
            ///         <li><b>ElementNameFilter: </b> not supported.</li>
            ///         <li><b>ItemName: </b> supported.</li>
            ///         <li><b>ItemPath: </b> supported.</li>
            ///         <li><b>LocaleID: </b> supported.</li>
            ///         <li><b>MaxElementsReturned: </b> ignored.</li>
            ///         <li><b>PropertyNames: </b> ignored.</li>
            ///         <li><b>ReturnAllProperties: </b> partially supported (assumed always <c>true</c>).</li>
            ///         <li><b>ReturnErrorText: </b> partially supported (assumed always <c>true</c>).</li>
            ///         <li><b>ReturnPropertyValues: </b> supported.</li>
            ///         <li><b>VendorField: </b> not supported.</li>
            ///     </ul>
            /// </para>
            /// 
            /// <para>
            ///     The following <c>WriteResponse</c> standard features are (or not) implemented.
            ///     <ul>
            ///         
            ///         <li>
            ///             <b>BrowseResult: </b> the following features are (or not) supported:
            ///             <ul>
            ///                 <li><b>ClientRequestHandle: </b> supported.</li>
            ///                 <li><b>RcvTime: </b> supported (evaluated as first instruction).</li>
            ///                 <li><b>ReplyTime: </b> supported (evaluated as last instruction).</li>
            ///                 <li><b>RevisedLocaleID: </b> supported (<c>en-us</c>).</li>
            ///                 <li><b>ServerState: </b> supported (<c>running</c>).</li>
            ///             </ul>
            ///         </li>
            /// 
            ///         <li><b>ContinuationPoint: </b> not supported.</li>
            ///         
            ///         <li>
            ///             <b>Elements: </b> the following features are (or not) supported:
            ///             <ul>
            ///                 <li><b>HasChildren: </b> supported.</li>
            ///                 <li><b>IsItem: </b> supported.</li>
            ///                 <li><b>ItemName: </b> supported.</li>
            ///                 <li><b>ItemPath</b> supported.</li>
            ///                 <li><b>Name: </b> not supported (always set <c>null</c>).</li>
            ///                 <li>
            ///                     <b>Properties: </b> 
            ///                     supported, but no standard ones. All available ItemProperties are always returned. Supported ItemProperties are a set of custom properties. 
            ///                     <ul>
            ///                         <li><b>Description: </b> supported.</li>
            ///                         <li><b>ItemName: </b> supported.</li>
            ///                         <li><b>ItemPath: </b> supported.</li>
            ///                         <li><b>Name: </b> supported.</li>
            ///                         <li><b>ResultID: </b> not supported.</li>
            ///                         <li><b>Value: </b> supported.</li>
            ///                     </ul>
            ///                 </li>
            ///             </ul>
            ///         </li>
            ///         
            ///         <li>
            ///             <b>Errors: </b> One error per time is reported. The following errors are supported:
            ///             <ul>
            ///                 <li><c>E_UNKNOWNITEMNAME</c></li>
            ///                 <li><c>E_OUTOFMEMORY</c></li>
            ///                 <li><c>E_FAIL</c></li>
            ///             </ul>
            ///         </li>
            ///         
            ///         <li><b>MoreElements: </b> not supported (always <c>false</c>).</li>
            ///     </ul>
            ///     
            /// </para>
            /// <para>
            /// <b>Please consider that, in our implementation, dead branches are not supposed to be.</b>
            /// </para>
            /// 
            /// <code>
            /// // The following code can be found in OCXSClient and 
            /// // shows how to call Browse service.
            /// class Program {
            ///
            ///     private const string EndpointAddress = "http://localhost/OCXS/OCXS.svc";
            ///     
            ///     static void Main(string[] args) {
            ///     
            ///         // The following browses the branch Root/Fold1/.
            ///         Browse("en-us", "0x00FF", "Root/Fold1/", "", true);
            ///     
            ///         // The following browses the item Root/Fold1/MyVar1.
            ///         Browse("en-us", "0x00FF", "Root/Fold1/", "MyVar1", true);
            ///     }
            ///     
            ///     public static BrowseResponse Browse(
            ///         string localeID,
            ///         string clientRequestHandle,
            ///         string itemPath,
            ///         string itemName,
            ///         bool returnPropertyValues,
            ///         // Not supported features formal parameters down here
            ///         System.Xml.XmlQualifiedName[] propertyNames = null,
            ///         string continuationPoint = "",
            ///         int maxElementsReturned = 0,
            ///         browseFilter browsingFilter = browseFilter.all,
            ///         string elementNameFilter = "",
            ///         string vendorFilter = "",
            ///         bool returnAllProperties = true,
            ///         bool returnErrorText = true)
            ///     {
            ///         // Get the channel.
            ///         // GetOCXSChannel can be found in OCXSClient.
            ///         IOCXSService svc = GetOCXSChannel(EndpointAddress);
            ///
            ///         // Create the request.
            ///         BrowseRequest BR = 
            ///             new BrowseRequest(
            ///                 propertyNames,
            ///                 localeID,
            ///                 clientRequestHandle,
            ///                 itemPath,
            ///                 itemName,
            ///                 continuationPoint,
            ///                 maxElementsReturned,
            ///                 browsingFilter,
            ///                 elementNameFilter,
            ///                 vendorFilter,
            ///                 returnAllProperties,
            ///                 returnPropertyValues,
            ///                 returnErrorText);
            ///
            ///         // Perform the call to service and get the response.
            ///         BrowseResponse res = svc.Browse(BR);
            ///
            ///         // Print the response.
            ///         // PrintResponse can be found in OCXSClient.
            ///         PrintResponse(res);
            ///
            ///         return res;
            ///     }
            /// }
            /// </code>
            /// 
            /// </remarks>
            /// <param name="request">The <c>BrowseRequest</c> issued by client according to OCP XML DA 1.0 specification.</param>
            /// <returns>A <c>BrowseResponse</c> according to OPC XML DA 1.0 specification.</returns>
            public BrowseResponse Browse(BrowseRequest request) {
                
                BrowseResponse BR = null;
                // Get the reply base
                ReplyBase RB = this.GetReplyBase(request.ClientRequestHandle, request.LocaleID);

                try {

                    List<BrowseElement> browseEls = new List<BrowseElement>();
                    bool unknownPath = false;

                    // Check if requested item is an Item.
                    // It is an Item if its ID matches a key in datastore.
                    if (this.dataStore.ContainsKey(request.ItemPath + request.ItemName)) {

                        // The pointed element is an item => return it and ALL its properties
                        ItemProperty[] props =
                            this.GetItemPropertiesByItemId(request.ItemPath, request.ItemName, request.ReturnPropertyValues);
                        browseEls = null;

                    } else {

                        // The pointed element is not an item => invalid path or branch?
                        HashSet<String> hs = this.GetBrowsingLevel(request.ItemPath + request.ItemName);

                        if (hs != null) {

                            // Not invalid
                            foreach (string el in hs) {

                                BrowseElement be = new BrowseElement();

                                if (this.dataStore.ContainsKey(el)) {
                                    // Item
                                    be.HasChildren = false;
                                    be.IsItem = true;
                                    be.ItemName = el;
                                    be.ItemPath = "";
                                    be.Name = null;
                                    be.Properties = this.GetItemPropertiesByItemId("", el, request.ReturnPropertyValues);
                                }
                                else {
                                    // Not item => branch
                                    be.HasChildren = true; // Dead branch in our implementation are not supposed to be.
                                    be.IsItem = false;
                                    be.ItemName = el;
                                    be.ItemPath = "";
                                    be.Name = null;
                                    be.Properties = null;
                                }

                                browseEls.Add(be);

                            }

                        } else {
                            // Unknown path was browsed
                            browseEls = null;
                            unknownPath = true;
                        }

                    }
                    
                    BR = new BrowseResponse(
                            RB, 
                            (browseEls != null) ? browseEls.ToArray() : null,
                            (unknownPath) ? new OPCError[] { this.BuildOPCError("E_UNKNOWNITEMNAME", string.Empty) } : null,
                            null,   // Continuation point
                            false); // More elements

                    RB.ReplyTime = DateTime.Now;

                    return BR;

                } catch (OutOfMemoryException e) {

                    RB.ReplyTime = DateTime.Now;

                    // Return proper out of memory error message.
                    return new BrowseResponse(
                            RB,
                            null,
                            new OPCError[] { this.BuildOPCError("E_OUTOFMEMORY", e.Message) },
                            null,   // Continuation point
                            false); // More elements 

                } catch (Exception e) {

                    RB.ReplyTime = DateTime.Now;

                    // Return proper out of memory error message.
                    return new BrowseResponse(
                            RB,
                            null,
                            new OPCError[] { this.BuildOPCError("E_FAIL", e.Message) },
                            null,   // Continuation point
                            false); // More elements
                }

            } /* Browse */

            /// <summary>
            /// Check if child element is a valid path (branch) or invalid one.
            /// </summary>
            /// <param name="path">The path being checked.</param>
            /// <returns>All valid items and branches inside the provided path or <c>null</c> if no items found.</returns>
            private HashSet<String> GetBrowsingLevel(string path) {

                HashSet<String> hs = new HashSet<string>();

                foreach (string k in this.dataStore.Keys) {

                    int pathOccurrance = k.IndexOf(path, 0);

                    if (pathOccurrance == 0) {

                        // Valid path => branch
                        int slashOccurrance = k.IndexOf("/", path.Length + 1);

                        if (slashOccurrance == -1) {

                            // Not found => child is item
                            hs.Add(k);

                        } else {

                            // Found => child is branch
                            hs.Add(k.Substring(0, slashOccurrance));

                        }

                    }

                }

                // If no element is found hs won't contain any element.
                if (hs.Count == 0)
                    hs = null;

                return hs;

            }

		}

	}

}
